# 开放平台：
import os
import time
import traceback
import weakref

import json
import requests
import logging
import queue
from threading import Thread
from . import (
    AsyncChatbotHandler,
    CallbackMessage,
    ChatbotMessage,
    Credential,
    AckMessage,
    DingTalkStreamClient,
)

try:
    from Panda.Core.Logme import Logger

    logger = Logger(
        level=Logger.LEVEL_DEBUG, filepath="panda.log", show_code=False, depth=-4
    )
except ImportError:
    logging.basicConfig(
        format="[%(asctime)s] %(filename)s -> line:%(lineno)d %(funcName)s() [%(levelname)s]: %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
        level=logging.INFO,
    )
    logger = logging.getLogger("ddbot")

"""消息类型"""
(
    sampleText,
    sampleImage,
    sampleMark,
    sampleLink,
    sampleCard,
    sampleAudio,
    sampleFile,
    sampleVideo,
) = (
    "sampleText",
    "sampleImageMsg",
    "sampleMarkdown",
    "sampleLink",
    "sampleActionCard",
    "sampleAudio",
    "sampleFile",
    "sampleVideo",
)
FILE, TEXT, AUDIO, PICTURE, VIDEO = "file", "text", "audio", "picture", "video"


class Template(object):

    @staticmethod
    def text(content):
        return str({"content": content})

    @staticmethod
    def link(text, url):
        return str(
            {
                "text": text,
                "title": text[:4],
                "picUrl": "@lALPDfJ6V_FPDmvNAfTNAfQ",
                "messageUrl": url,
            }
        )

    @staticmethod
    def markdown(title, text):
        return str(
            {
                "title": title,
                "text": text,
            }
        )

    @staticmethod
    def card(text):
        return str(
            {
                "title": "title",
                "text": text,
                "singleTitle": "查看详情",
                "singleURL": "https://www.baidu.com/",
            }
        )

    @staticmethod
    def file(path, token=None):
        data = dict({"mediaId": "", "fileType": "", "fileName": ""})
        if os.path.exists(path):
            data["fileName"] = os.path.split(path)[-1]
            data["fileType"] = data["fileName"].split(".")[-1]
            data["mediaId"] = Core.get_media_id(path, token)
        logger.info(data)
        return str(data)

    @staticmethod
    def image(path, token=None):
        if os.path.exists(path):
            url = Core.get_media_id(path, token)
        else:
            url = path
            logger.info(url)
        return str({"photoURL": url})


class Message(dict):

    def __init__(self, *args):
        super(Message, self).__init__(*args)

    def __getattr__(self, item):
        try:
            value = self[item]
            if isinstance(value, dict):
                return Message(value)
            else:
                return value
        except KeyError:
            logger.error(f"{self} No key:{item}")

    @property
    def text(self):
        return self.get("text", {}).get("content")

    @property
    def filename(self):
        if "fileName" in self["content"]:
            return self["content"]["fileName"]
        else:
            return f"{self["createAt"]}.file"

    @property
    def down_code(self):
        return self["content"]["downloadCode"]

    @property
    def webhook(self):
        return self["sessionWebhook"]

    @property
    def nick(self):
        return self["senderNick"]

    @property
    def msg_id(self):
        return self["msgId"]

    @property
    def conversation_id(self):
        return self["conversationId"]

    @property
    def conversation_type(self):
        return self["conversationType"]

    @property
    def admin(self):
        return self["isAdmin"]

    @property
    def create_time(self):
        return self["createAt"]

    @property
    def type(self):
        return self["msgtype"]

    @property
    def sender(self):
        return self["senderStaffId"]

    def recall(self):
        if self.conversationType == "1":
            """人机交互"""
            data = {k: self[k] for k in self if k in ["processQueryKeys", "robotCode"]}
            url = "https://api.dingtalk.com/v1.0/robot/otoMessages/batchRecall"
        else:
            """群内交互"""
            data = {
                k: self[k]
                for k in self
                if k in ["processQueryKeys", "openConversationId", "robotCode"]
            }
            url = "https://api.dingtalk.com/v1.0/robot/groupMessages/recall"
        # logger.info(f'{data}')
        self.core.post(data, url)

    def send_msg(self, msg):
        self.core.send_msg(msg)

    def get_down_url(self):
        self.core.get_down_url(self.msgs)


class Core(AsyncChatbotHandler):

    def __init__(self, code, secret):
        super(Core, self).__init__()
        self.robotCode, self.robotSecret = code, secret
        self.msgList = queue.Queue(-1)
        self.msgs = weakref.ref(Message)()
        self.temp = Template()
        self.functionDict = {"Private": {}, "Group": {}}
        self.token = self.get_app_token()
        self.token_expire = time.time() + 120
        self.header = None

    def get_app_token(self) -> str:
        """获取钉钉内部应用的token"""
        url = "https://api.dingtalk.com/v1.0/oauth2/accessToken"
        js = {"appKey": self.robotCode, "appSecret": self.robotSecret}
        res = requests.post(url=url, json=js)
        if res.status_code == 200:
            data = res.json()
            return data["accessToken"]

    @classmethod
    def get_media_id(cls, path: str, token) -> str:
        """获取上传图片的id"""
        url = (
            r"https://oapi.dingtalk.com/media/upload?access_token=%s&type=file" % token
        )
        files = {"media": open(path, "rb")}
        data = {"access_token": token, "type": "file"}
        response = requests.post(url, files=files, data=data)
        js = response.json()
        logger.info(js)
        return js["media_id"]

    def get_down_url(self, msg: Message):

        data = {
            "downloadCode": msg.down_code,
            "robotCode": self.msgs.robotCode,
        }
        url = "https://api.dingtalk.com/v1.0/robot/messageFiles/download"
        r = self.post(data, url)
        logger.critical(r["processQueryKeys"][0]["downloadUrl"])
        down_url = r["processQueryKeys"][0]["downloadUrl"]
        res = requests.get(down_url)
        with open(msg.filename, "wb") as f:
            f.write(res.content)

    def post(self, data, url=""):
        self.update_token()
        self.header = {
            "Content-Type": "application/json",
            "x-acs-dingtalk-access-token": self.token,
        }
        if url:
            data, url = data, url
        elif self.msgs.conversationType == "1":
            """人机交互"""
            url = "https://api.dingtalk.com//v1.0/robot/oToMessages/batchSend"
            data["userIds"] = [self.msgs.senderStaffId]
        else:
            """群内交互"""
            url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send"
            data["openConversationId"] = self.msgs.conversationId
        logger.info(f"{url}\n{data}")

        r = requests.post(url, data=json.dumps(data), headers=self.header)
        logger.warning(r.json())
        msg_id = r.json().get("processQueryKey", "") or r.json()
        return Message(
            {
                "processQueryKeys": [msg_id],
                "openConversationId": self.msgs.conversationId,
                "robotCode": self.msgs.robotCode,
                "conversationType": self.msgs.conversationType,
                "core": self,
            }
        )

    def send_msg(self, text):
        logger.debug(text)
        data = {
            "robotCode": self.robotCode,
            "msgKey": sampleText,
            "msgParam": str(Template.text(text)),
        }
        return self.post(data)

    def send_card(self, text):
        data = {
            "robotCode": self.robotCode,
            "msgKey": sampleCard,
            "msgParam": Template.card(text),
        }
        return self.post(data)

    def send_link(self, text, link):
        data = {
            "robotCode": self.robotCode,
            "msgKey": sampleLink,
            "msgParam": str(Template.link(text, link)),
        }
        return self.post(data)

    def update_token(self):
        if time.time() > self.token_expire:
            self.token = self.get_app_token()
            logger.info(self.token)
            self.token_expire = time.time() + 120

    def send_file(self, path):
        self.update_token()
        data = {
            "robotCode": self.robotCode,
            "msgKey": sampleFile,
            "msgParam": Template.file(path, self.token),
        }
        return self.post(data)

    def send_image(self, path):
        self.update_token()
        data = {
            "robotCode": self.robotCode,
            "msgKey": sampleImage,
            "msgParam": Template.image(path, self.token),
        }
        return self.post(data)

    def process(self, message: CallbackMessage):
        msg_dict = ChatbotMessage.from_dict(message.data)
        self.msgList.put(msg_dict)
        return AckMessage.STATUS_OK, "ok"

    def start_reply(self):
        """信息通讯和自动回复"""
        while True:
            try:
                msg = Message(self.msgList.get(timeout=1).to_dict())
            except queue.Empty:
                time.sleep(0.1)
            else:
                try:
                    self.msgs = weakref.ref(msg)()
                    msg.user = weakref.ref(self)()  # 反向注入,用于撤回
                except:  # noqa
                    logger.error(traceback.format_exc())
                else:
                    # logger.warning(msg)
                    self.configure(msg)

    def configure(self, _msg):
        if _msg.conversation_type == "1":
            reply_fn = self.functionDict["Private"].get(_msg.type)
        elif _msg.conversation_type == "2":
            reply_fn = self.functionDict["Group"].get(_msg.type)
        else:
            reply_fn = None
        logger.info(f"{reply_fn}")
        if reply_fn:
            try:
                res = reply_fn(_msg)
                if res:
                    self.send_msg(res)
            except:  # noqa
                logger.error(traceback.format_exc())

    def msg_register(self, msg_types, is_private=False, is_group=False):
        """Register a function to handle a specific message type."""
        if not (isinstance(msg_types, list) or isinstance(msg_types, tuple)):
            msg_types = [msg_types]  # 如果只有一项则变成列表,类型必须为列表形式

        def _msg_register(
            func,
        ):  # self.functionDict = {'FriendChat': {}, 'GroupChat': {}, 'MpChat': {}}
            for _type in msg_types:
                if is_private:
                    self.functionDict["Private"][_type] = func
                if is_group:
                    self.functionDict["Group"][_type] = func
            return func

        return _msg_register

    def run(self, callback=None):
        credit = Credential(self.robotCode, self.robotSecret)
        client = DingTalkStreamClient(credit)
        client.register_callback_handler(ChatbotMessage.TOPIC, self)
        client.register_all_event_handler(self)
        Thread(target=self.start_reply).start()
        if hasattr(callback, "__call__"):
            callback()
        Thread(target=client.start_forever()).start()


if __name__ == "__main__":
    import uuid

    print(uuid.uuid1())
